ADVERTISEMENT
Responsive Ad Unit • Modern C++ Toolchains & Compilers
🎯 Core Learning Objectives
  • Master ISO C++ standards, compiler mechanics, and idioms (C++11/14/17/20).
  • Implement memory-safe patterns, RAII, dynamic polymorphism, and generic templates.
  • Analyze assembly output, cache efficiency, and move semantics.
  • Demonstrate conceptual mastery via the complete 12-question technical assessment.

4.1. Classes and Objects

In C++, classes and objects are fundamental concepts of Object-Oriented Programming (OOP), which focuses on organizing code into reusable and modular structures. A class is a blueprint for creating objects, which are instances of the class. The class defines the properties (attributes) and behaviors (methods) that the objects will have.

4.1.1. Classes in C++

A class is a user-defined data type that represents a collection of related data and functions. The data is called member variables (attributes), and the functions are called member functions (methods). Classes provide a way to structure the data and the operations that can be performed on that data.

Syntax of a class:

C++ Source
class ClassName {
   private:
      // Private members (cannot be accessed directly from outside the class)
      dataType memberVariable;

   public:
      // Public members (can be accessed from outside the class)
      void memberFunction() {
         // function definition
      }
};
  • Private Members: These are attributes or methods that cannot be accessed directly from outside the class. They are used for encapsulation, which helps in hiding the internal workings of the class.
  • Public Members: These can be accessed from outside the class. They are used to define functions that can be called by the user of the class.

2. Objects in C++

An object is an instance of a class. When you create an object of a class, the class defines the properties (variables) and methods (functions) that the object will have. Objects allow you to store data and interact with it using the methods defined in the class.

Syntax for creating objects:

C++ Source
ClassName objectName;  // Object creation

3. Example of Classes and Objects in C++

Let’s take a simple example where we define a Car class and then create objects (specific cars) from it.

C++ Source
#include <iostream>
using namespace std;

// Define the Car class
class Car {
   // Private members
   private:
      string brand;
      string model;
      int year;

   // Public members
   public:
      // Constructor to initialize the object
      Car(string b, string m, int y) {
         brand = b;
         model = m;
         year = y;
      }

      // Member function to display car details
      void displayDetails() {
         cout << "Car brand: " << brand << endl;
         cout << "Car model: " << model << endl;
         cout << "Car year: " << year << endl;
      }

      // Getter functions
      string getBrand() {
         return brand;
      }

      string getModel() {
         return model;
      }

      int getYear() {
         return year;
      }
};

int main() {
   // Create objects of the Car class
   Car car1("Toyota", "Corolla", 2020);  // Initialize car1 with brand, model, and year
   Car car2("Honda", "Civic", 2021);     // Initialize car2 with brand, model, and year

   // Calling member function using objects
   car1.displayDetails();
   car2.displayDetails();

   // Accessing object properties through getter functions
   cout << "Brand of car1: " << car1.getBrand() << endl;
   cout << "Year of car2: " << car2.getYear() << endl;

   return 0;
}

Explanation of the Example:

  • Class Definition:
    • The Car class contains three private data members: brand, model, and year.
    • A constructor is used to initialize the object's attributes when the object is created.
    • The displayDetails() function is a member function that prints the details of the car.
    • Getter functions (getBrand(), getModel(), getYear()) are used to access the private attributes from outside the class.
  • Object Creation:
    • Car car1("Toyota", "Corolla", 2020); creates an object car1 of the class Car with the specified values for brand, model, and year.
    • Similarly, Car car2("Honda", "Civic", 2021); creates another object car2.
  • Calling Member Functions:
    • The displayDetails() function is called on both objects car1 and car2 to display their details.
    • The getter functions are used to access the private member variables.

4.1.2. Access Modifiers in C++

Access modifiers determine the visibility of class members (attributes and methods). C++ supports three access modifiers:

  • Private: Members are accessible only within the class itself and not from outside.
  • Public: Members are accessible from anywhere, both inside and outside the class.
  • Protected: Members are accessible in the class and by derived classes (used in inheritance).

4.2. Constructor and Destructor

  • Constructor: A special member function that is automatically called when an object of the class is created. It is used to initialize the object.
  • Destructor: A special member function that is automatically called when an object is destroyed. It is used to clean up resources.

Example of constructor and destructor:

C++ Source
class Car {
   private:
      string brand;
      string model;
      int year;

   public:
      // Constructor
      Car(string b, string m, int y) {
         brand = b;
         model = m;
         year = y;
         cout << "Car object created!" << endl;
      }

      // Destructor
      ~Car() {
         cout << "Car object destroyed!" << endl;
      }

      // Function to display car details
      void displayDetails() {
         cout << "Car brand: " << brand << endl;
         cout << "Car model: " << model << endl;
         cout << "Car year: " << year << endl;
      }
};

6. Other Key Points:

  • Member Initialization: C++ allows for member initialization using the constructor to ensure that objects are created with valid data.
  • Encapsulation: Classes in C++ support encapsulation, where data is hidden within the class, and access is provided through public methods (getter and setter functions).
  • Inheritance and Polymorphism: C++ classes can inherit properties from other classes, and polymorphism allows objects to behave in different ways based on their class type.

Conclusion: Classes and objects in C++ provide a powerful way to organize and manage code in a structured, reusable way. By using classes, we can define complex types, encapsulate data, and provide methods to interact with the data, which is essential for implementing Object-Oriented Programming principles.

4.2.1. Default, parameterized, and copy constructors

In C++, constructors are special member functions that are automatically called when an object of a class is created. Their primary purpose is to initialize the object. Constructors have the same name as the class and do not return any type, not even void.

There are three types of constructors in C++:

  • Default Constructor
  • Parameterized Constructor
  • Copy Constructor

1. Default Constructor

A default constructor is a constructor that does not take any arguments. If you don't define any constructor in a class, C++ will automatically generate a default constructor for you. However, if you define any constructor, the default constructor is not generated automatically, unless explicitly defined.

A default constructor initializes the object with default values. If no explicit values are provided for the attributes of an object, the default constructor initializes them to some predefined values (like 0 for integers, null for pointers, etc.).

Syntax of a Default Constructor:

C++ Source
class ClassName {
public:
    ClassName() {
        // Initialization of member variables (optional)
    }
};

Example of Default Constructor:

C++ Source
#include <iostream>
using namespace std;

class Car {
private:
    string brand;
    string model;
    int year;

public:
    // Default Constructor
    Car() {
        brand = "Unknown";
        model = "Unknown";
        year = 0;
    }

    // Function to display car details
    void displayDetails() {
        cout << "Car brand: " << brand << endl;
        cout << "Car model: " << model << endl;
        cout << "Car year: " << year << endl;
    }
};

int main() {
    Car car1; // Object created with default constructor
    car1.displayDetails(); // Displays default values
    return 0;
}

Explanation:

  • The Car() constructor is a default constructor. It initializes the attributes of Car with default values: "Unknown" for brand, "Unknown" for model, and 0 for year.
  • When the object car1 is created, this constructor is called, and the object is initialized with default values.

2. Parameterized Constructor

A parameterized constructor is a constructor that takes one or more arguments. It allows you to initialize an object with specific values at the time of its creation. This provides flexibility, as the object can be initialized with different values based on the parameters passed.

Syntax of a Parameterized Constructor:

C++ Source
class ClassName {
public:
    ClassName(type parameter1, type parameter2) {
        // Initialize members with parameters
    }
};

Example of Parameterized Constructor:

C++ Source
#include <iostream>
using namespace std;

class Car {
private:
    string brand;
    string model;
    int year;

public:
    // Parameterized Constructor
    Car(string b, string m, int y) {
        brand = b;
        model = m;
        year = y;
    }

    // Function to display car details
    void displayDetails() {
        cout << "Car brand: " << brand << endl;
        cout << "Car model: " << model << endl;
        cout << "Car year: " << year << endl;
    }
};

int main() {
    // Creating objects with different initial values using parameterized constructor
    Car car1("Toyota", "Corolla", 2020);
    Car car2("Honda", "Civic", 2021);

    car1.displayDetails();
    car2.displayDetails();

    return 0;
}

Explanation:

  • The constructor Car(string b, string m, int y) is a parameterized constructor that initializes the object with the values passed to it when the object is created.
  • The object car1 is created with the values "Toyota", "Corolla", and 2020 for brand, model, and year, respectively.
  • The object car2 is created similarly with "Honda", "Civic", and 2021.

3. Copy Constructor

A copy constructor is a special constructor that initializes an object using another object of the same class. It creates a new object as a copy of an existing object, and it is invoked when:

  • A new object is created as a copy of an existing object.
  • An object is passed by value to a function.
  • A function returns an object by value.

The copy constructor performs a shallow copy or deep copy depending on the type of members (whether they are simple data types or complex types like pointers).

Syntax of a Copy Constructor:

C++ Source
class ClassName {
public:
    ClassName(const ClassName& otherObject) {
        // Copy constructor code to copy data from otherObject
    }
};

Example of Copy Constructor:

C++ Source
#include <iostream>
using namespace std;

class Car {
private:
    string brand;
    string model;
    int year;

public:
    // Parameterized Constructor
    Car(string b, string m, int y) {
        brand = b;
        model = m;
        year = y;
    }

    // Copy Constructor
    Car(const Car& other) {
        brand = other.brand;
        model = other.model;
        year = other.year;
        cout << "Copy constructor called!" << endl;
    }

    // Function to display car details
    void displayDetails() {
        cout << "Car brand: " << brand << endl;
        cout << "Car model: " << model << endl;
        cout << "Car year: " << year << endl;
    }
};

int main() {
    Car car1("Toyota", "Corolla", 2020);
    Car car2 = car1;  // Copy constructor is called here

    car1.displayDetails();
    car2.displayDetails();

    return 0;
}

Explanation:

  • The copy constructor Car(const Car& other) is called when car2 is created as a copy of car1. The constructor initializes car2 with the values of car1's attributes (brand, model, and year).
  • When Car car2 = car1; is executed, the copy constructor is called to copy the values of car1 into car2.

Key Points about Copy Constructor:

  • If you don't define a copy constructor, C++ provides a default one, which does a shallow copy of the object.
  • A shallow copy means that the members of the object (including pointers) are copied directly. If there are dynamic resources (like arrays or memory allocated via pointers), it can lead to problems like double-deletion of resources when the object is destroyed.
  • A deep copy involves creating copies of dynamically allocated resources to prevent such issues, which can be achieved by manually implementing the copy constructor.

Example of a Deep Copy Constructor (in case of pointers):

C++ Source
class Car {
private:
    string* brand;
    string* model;

public:
    // Parameterized Constructor
    Car(string b, string m) {
        brand = new string(b);
        model = new string(m);
    }

    // Copy Constructor (Deep Copy)
    Car(const Car& other) {
        brand = new string(*(other.brand));  // Create new memory for brand
        model = new string(*(other.model));  // Create new memory for model
    }

    // Destructor to release dynamically allocated memory
    ~Car() {
        delete brand;
        delete model;
    }

    // Function to display car details
    void displayDetails() {
        cout << "Car brand: " << *brand << endl;
        cout << "Car model: " << *model << endl;
    }
};

Conclusion:

  • Default Constructor: Initializes the object with default values.
  • Parameterized Constructor: Initializes the object with specified values.
  • Copy Constructor: Initializes a new object as a copy of an existing object. The copy constructor can either perform a shallow copy (default) or a deep copy (when dealing with dynamic memory allocation).

Understanding constructors is crucial for managing object initialization and ensuring the correct handling of resources in object-oriented programming.

4.2.2. Destructor in C++ Programming

A destructor in C++ is a special member function of a class that is executed when an object of that class goes out of scope or is explicitly deleted. It is used to release any resources (such as memory, file handles, etc.) that were acquired during the lifetime of an object.

Key Features of Destructors:

  • Name: The name of a destructor is the same as the class name, preceded by a tilde (~). For example, if the class is MyClass, the destructor will be ~MyClass().
  • No Return Type: A destructor has no return type, not even void.
  • No Arguments: Destructors do not accept any parameters, and therefore cannot be overloaded (you can only have one destructor per class).
  • Automatic Call: Destructors are automatically invoked when an object goes out of scope or when the delete operator is used on a dynamically allocated object.
  • No Overloading: Since destructors have no parameters, they cannot be overloaded like constructors.

Purpose of Destructor

The primary purpose of a destructor is to perform cleanup tasks for the object before it is destroyed. This includes:

  • Deallocating memory that was dynamically allocated using new or malloc.
  • Closing files that were opened during the lifetime of the object.
  • Releasing other resources, such as network connections or locks.

Syntax of Destructor

The syntax of a destructor is as follows:

C++ Source
~ClassName()
{
    // code to release resources
}

Example: Destructor in Action

Here's a simple example demonstrating the use of destructors:

Example 1: Basic Destructor Example

C++ Source
#include <iostream>

class MyClass
{
public:
    MyClass() { 
        std::cout << "Constructor called!" << std::endl; 
    }
    
    ~MyClass() { 
        std::cout << "Destructor called!" << std::endl; 
    }
};

int main() {
    MyClass obj; // Constructor is called here
    // Destructor will be called automatically when 'obj' goes out of scope
    return 0;
}

Output:

C++ Source
Constructor called!
Destructor called!

In this example:

  • The constructor is invoked when the object obj is created.
  • The destructor is automatically called when obj goes out of scope at the end of main().

Example 2: Destructor with Dynamic Memory Management

The real usefulness of destructors appears when they handle dynamic memory allocated using new or malloc. If an object allocates memory dynamically, its destructor should deallocate that memory to avoid memory leaks.

C++ Source
#include <iostream>

class MyClass
{
private:
    int* data; // Pointer to dynamically allocated memory
    
public:
    MyClass(int size) {
        data = new int[size]; // Allocate memory dynamically
        std::cout << "Memory allocated!" << std::endl;
    }
    
    ~MyClass() {
        delete[] data;  // Deallocate memory in the destructor
        std::cout << "Memory deallocated!" << std::endl;
    }
};

int main() {
    MyClass obj(5); // Constructor is called, memory allocated for 5 integers
    // Destructor will be called automatically, and memory will be freed
    return 0;
}

Output:

C++ Source
Memory allocated!
Memory deallocated!

Here:

  • The constructor dynamically allocates an array of integers.
  • The destructor deallocates that memory using delete[].

Example 3: Destructor with File Handling

Destructors are also useful for closing files or releasing other system resources.

C++ Source
#include <iostream>
#include <fstream>

class FileHandler {
private:
    std::ofstream file;
    
public:
    FileHandler(const std::string& filename) {
        file.open(filename);  // Open the file
        if (file.is_open()) {
            std::cout << "File opened!" << std::endl;
        }
    }
    
    ~FileHandler() {
        if (file.is_open()) {
            file.close();  // Close the file in the destructor
            std::cout << "File closed!" << std::endl;
        }
    }
};

int main() {
    FileHandler fileHandler("example.txt"); // File opened here
    // File will be automatically closed when the object goes out of scope
    return 0;
}

Output:

C++ Source
File opened!
File closed!

In this example:

  • The constructor opens a file for writing.
  • The destructor ensures that the file is closed when the FileHandler object is destroyed.

Destructor and Dynamic Memory Management

When using dynamic memory allocation (i.e., memory allocated using new), it's important to ensure that the memory is properly deallocated to avoid memory leaks. The destructor is typically used for this purpose.

Example 4: Using delete and delete[]

C++ Source
#include <iostream>

class MyClass
{
private:
    int* data; // Pointer to dynamically allocated memory
    
public:
    MyClass() {
        data = new int(10);  // Allocates a single integer on the heap
        std::cout << "Single integer allocated!" << std::endl;
    }
    
    ~MyClass() {
        delete data;  // Deallocate memory
        std::cout << "Memory deallocated!" << std::endl;
    }
};

int main() {
    MyClass obj; // Constructor allocates memory
    // Destructor will deallocate memory automatically when the object goes out of scope
    return 0;
}

Output:

C++ Source
Single integer allocated!
Memory deallocated!

In this example, new allocates memory for a single integer, and the destructor releases it using delete.

Destructor for Base and Derived Classes (Polymorphism)

When using inheritance, it's important to ensure that the destructor of the base class is virtual if you are dealing with polymorphic objects. This ensures that the correct destructor is called for objects created with base class pointers that point to derived class objects.

Example 5: Virtual Destructor for Inheritance

C++ Source
#include <iostream>

class Base {
public:
    Base() { std::cout << "Base class constructor!" << std::endl; }
    virtual ~Base() { std::cout << "Base class destructor!" << std::endl; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived class constructor!" << std::endl; }
    ~Derived() { std::cout << "Derived class destructor!" << std::endl; }
};

int main() {
    Base* obj = new Derived();  // Base pointer points to Derived object
    delete obj; // Correct destructor (Derived's) will be called
    return 0;
}

Output:

C++ Source
Base class constructor!
Derived class constructor!
Derived class destructor!
Base class destructor!

In this example:

  • The destructor of Base is declared virtual, ensuring that the Derived class destructor is called when the object is deleted using a base class pointer.
  • Without a virtual destructor, only the base class destructor would be called, potentially leading to resource leaks.

Conclusion: Destructors are an essential feature in C++ for resource management, particularly for cleaning up dynamically allocated memory or other resources (such as file handles, network connections, etc.). They are automatically called when objects go out of scope, and their proper use prevents memory leaks and ensures the safe release of resources.

4.3. Inheritance

Inheritance is one of the key features of Object-Oriented Programming (OOP) in C++. It allows one class to inherit properties and behaviors (i.e., data members and member functions) from another class. This mechanism promotes code reuse and creates a relationship between classes, where one class (derived class) is a specialized version of another class (base class).

Key Concepts in Inheritance:

  • Base Class (Parent Class): This is the class whose properties and methods are inherited by another class.
  • Derived Class (Child Class): This class inherits the properties and methods of the base class and can have additional properties or methods of its own.
  • Access Modifiers: Inheritance is controlled by access specifiers: public, protected, and private. These define how members of the base class can be accessed from the derived class.

Types of Inheritance:

  • Single Inheritance: A derived class inherits from only one base class.
  • Multiple Inheritance: A derived class inherits from more than one base class.
  • Multilevel Inheritance: A class is derived from another derived class.
  • Hierarchical Inheritance: Multiple classes are derived from a single base class.
  • Hybrid Inheritance: A combination of two or more types of inheritance.

Syntax of Inheritance:

C++ Source
class BaseClass {
  // Members and methods of the base class
};

class DerivedClass : accessSpecifier BaseClass {
  // Members and methods of the derived class
};

Access Specifiers in Inheritance:

  • public inheritance: All public and protected members of the base class are accessible as public and protected members in the derived class.
  • protected inheritance: All public and protected members of the base class become protected in the derived class.
  • private inheritance: All public and protected members of the base class become private in the derived class.

Example of Inheritance in C++

1. Single Inheritance

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal {  // Dog is derived from Animal
public:
    void bark() {
        cout << "Dog barks" << endl;
    }
};

int main() {
    Dog myDog;
    myDog.speak();  // Inherited method
    myDog.bark();   // Dog's own method
    return 0;
}

Output:

C++ Source
Animal speaks
Dog barks

In this example: Dog is a derived class that inherits the speak method from the Animal class. The Dog class also defines its own method bark.

2. Multiple Inheritance

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Bird {
public:
    void fly() {
        cout << "Bird flies" << endl;
    }
};

class Bat : public Animal, public Bird {  // Inherits from both Animal and Bird
public:
    void hang() {
        cout << "Bat hangs upside down" << endl;
    }
};

int main() {
    Bat myBat;
    myBat.speak();   // Inherited from Animal
    myBat.fly();     // Inherited from Bird
    myBat.hang();    // Bat's own method
    return 0;
}

Output:

C++ Source
Animal speaks
Bird flies
Bat hangs upside down

In this example: Bat inherits from both Animal and Bird. The Bat class can access the methods from both base classes (speak from Animal and fly from Bird).

3. Multilevel Inheritance

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Mammal : public Animal {
public:
    void walk() {
        cout << "Mammal walks" << endl;
    }
};

class Dog : public Mammal {  // Dog is derived from Mammal, which is derived from Animal
public:
    void bark() {
        cout << "Dog barks" << endl;
    }
};

int main() {
    Dog myDog;
    myDog.speak();  // Inherited from Animal
    myDog.walk();   // Inherited from Mammal
    myDog.bark();   // Dog's own method
    return 0;
}

Output:

C++ Source
Animal speaks
Mammal walks
Dog barks

In this example: Dog inherits from Mammal, which in turn inherits from Animal. Dog has access to all the methods of Animal and Mammal.

4. Hierarchical Inheritance

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "Dog barks" << endl;
    }
};

class Cat : public Animal {
public:
    void meow() {
        cout << "Cat meows" << endl;
    }
};

int main() {
    Dog myDog;
    Cat myCat;
    myDog.speak();  // Inherited from Animal
    myDog.bark();   // Dog's own method
    myCat.speak();  // Inherited from Animal
    myCat.meow();   // Cat's own method
    return 0;
}

Output:

C++ Source
Animal speaks
Dog barks
Animal speaks
Cat meows

In this example: Both Dog and Cat inherit from the same Animal class. Each derived class (Dog and Cat) has its own method in addition to the inherited speak method.

Constructor and Destructor in Inheritance

  • Constructor: The base class constructor is called first, followed by the derived class constructor.
  • Destructor: The derived class destructor is called first, followed by the base class destructor.

Example with Constructors and Destructors:

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    Animal() {
        cout << "Animal Constructor" << endl;
    }
    ~Animal() {
        cout << "Animal Destructor" << endl;
    }
};

class Dog : public Animal {
public:
    Dog() {
        cout << "Dog Constructor" << endl;
    }
    ~Dog() {
        cout << "Dog Destructor" << endl;
    }
};

int main() {
    Dog myDog; // Object creation will call constructors
    return 0;
}

Output:

C++ Source
Animal Constructor
Dog Constructor
Dog Destructor
Animal Destructor

Here, the constructor of the Animal class is called first, then the Dog constructor, and when the object goes out of scope, the destructors are called in reverse order.

Conclusion: Inheritance in C++ allows you to build relationships between classes, promote code reuse, and extend existing code with new functionality. The derived class inherits properties and behaviors from the base class and can override or extend them as needed.

4.4. Polymorphism

Polymorphism is one of the core concepts of Object-Oriented Programming (OOP), and it allows objects of different classes to be treated as objects of a common base class. The term polymorphism comes from the Greek words "poly" (many) and "morph" (form), meaning many forms. In C++, polymorphism enables a function or an operator to behave differently depending on the type of object that it is acting upon.

There are two types of polymorphism in C++:

  • Compile-time Polymorphism (Static Polymorphism)
  • Runtime Polymorphism (Dynamic Polymorphism)

Let's explore both in detail.

1. Compile-time Polymorphism (Static Polymorphism)

Compile-time polymorphism is resolved during the compilation process. This is primarily achieved through function overloading and operator overloading.

Function Overloading

Function overloading allows multiple functions with the same name but different parameters (either in number or type) to exist in the same scope. The compiler determines which function to call based on the arguments passed at compile time.

Example: Function Overloading

C++ Source
#include <iostream>
using namespace std;

class Printer {
public:
    void print(int i) {
        cout << "Printing integer: " << i << endl;
    }

    void print(double d) {
        cout << "Printing double: " << d << endl;
    }

    void print(string s) {
        cout << "Printing string: " << s << endl;
    }
};

int main() {
    Printer printer;
    printer.print(10);         // Calls print(int)
    printer.print(3.14);       // Calls print(double)
    printer.print("Hello!");   // Calls print(string)

    return 0;
}

Output:

C++ Source
Printing integer: 10
Printing double: 3.14
Printing string: Hello!

In the above example, the print function is overloaded to accept different types of arguments. The compiler determines which version of the function to invoke based on the passed arguments.

Operator Overloading

Operator overloading allows you to define custom behaviors for operators when they are used with user-defined objects.

Example: Operator Overloading

C++ Source
#include <iostream>
using namespace std;

class Complex {
private:
    int real, imag;
    
public:
    Complex() : real(0), imag(0) {}

    Complex(int r, int i) : real(r), imag(i) {}

    // Overload the "+" operator to add two complex numbers
    Complex operator + (Complex const& obj) {
        return Complex(real + obj.real, imag + obj.imag);
    }

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3, 4), c2(1, 2);
    Complex c3 = c1 + c2; // Using overloaded "+" operator
    c3.display(); // Outputs: 4 + 6i

    return 0;
}

Output:

C++ Source
4 + 6i

In this example, we overloaded the + operator for the Complex class to add two complex numbers.

2. Runtime Polymorphism (Dynamic Polymorphism)

Runtime polymorphism is achieved through inheritance and virtual functions. It allows a function to behave differently based on the type of object it is called on during execution, rather than at compile time.

Virtual Functions

In C++, a virtual function is a member function of a class that you expect to be overridden in derived classes. By marking a function as virtual, the C++ compiler ensures that the correct function is called for an object, even when the object is accessed through a pointer or reference to a base class.

Example: Runtime Polymorphism with Virtual Functions

C++ Source
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() { // Virtual function
        cout << "Animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override { // Override the virtual function
        cout << "Dog barks." << endl;
    }
};

class Cat : public Animal {
public:
    void sound() override { // Override the virtual function
        cout << "Cat meows." << endl;
    }
};

int main() {
    Animal* animal;
    Dog dog;
    Cat cat;

    // Pointer of base class type can point to derived class objects
    animal = &dog;
    animal->sound();  // Outputs: Dog barks.

    animal = &cat;
    animal->sound();  // Outputs: Cat meows.

    return 0;
}

Output:

C++ Source
Dog barks.
Cat meows.

In this example, the sound function is declared as virtual in the Animal base class. When calling sound() through the base class pointer (animal), the actual function invoked depends on the type of object the pointer is pointing to (Dog or Cat). This is resolved at runtime, hence it's called runtime polymorphism.

Pure Virtual Functions and Abstract Classes

A pure virtual function is a function declared in a class that has no definition in that class. The class containing pure virtual functions is known as an abstract class. This forces derived classes to provide an implementation for that function.

Example: Abstract Class and Pure Virtual Function

C++ Source
#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() = 0;  // Pure virtual function, makes Shape an abstract class
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a Circle." << endl;
    }
};

class Square : public Shape {
public:
    void draw() override {
        cout << "Drawing a Square." << endl;
    }
};

int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Square();

    shape1->draw();  // Outputs: Drawing a Circle.
    shape2->draw();  // Outputs: Drawing a Square.

    delete shape1;
    delete shape2;

    return 0;
}

Output:

C++ Source
Drawing a Circle.
Drawing a Square.

In this example, Shape is an abstract class with a pure virtual function draw(). Both Circle and Square provide implementations for draw(), and we can create pointers to Shape that can point to objects of either derived class.

Key Points of Polymorphism:

  • Function Overloading: Same function name but different parameters (resolved at compile time).
  • Operator Overloading: Custom behavior for operators for user-defined types (resolved at compile time).
  • Virtual Functions: Used for runtime polymorphism, enabling method overriding in derived classes (resolved at runtime).
  • Pure Virtual Functions and Abstract Classes: Forces derived classes to implement specific functions.

Benefits of Polymorphism:

  • Code Reusability: You can use the same interface for different data types, reducing code duplication.
  • Extensibility: It's easy to add new classes and functionality without modifying existing code.
  • Flexibility: The same code can work with different types of objects, which enhances the adaptability of the program.

In summary, polymorphism enhances the flexibility and maintainability of object-oriented systems by allowing functions to operate on different object types without needing to know their exact types at compile time.

ADVERTISEMENT
Responsive Ad Unit • Software Engineering & High-Performance Systems
πŸ“ Concept Mastery Assessment
12 Questions Score: 0 / 0

Select your answer for each question below to receive immediate correctness evaluation and in-depth technical rationale.

Question 1 of 12
What are the three standard access specifiers in a C++ class?
Explanation
C++ classes control member visibility using 'public' (accessible anywhere), 'private' (accessible only inside the class), and 'protected' (accessible to derived classes).
Question 2 of 12
What is the default access level for members of a 'class' vs members of a 'struct' in C++?
Explanation
In C++, the ONLY semantic difference between a struct and a class is that struct members/inheritance default to public, while class defaults to private.
Question 3 of 12
What is a constructor in C++?
Explanation
Constructors initialize object member variables and acquire resources when an object is created. They have no return type.
Question 4 of 12
What is a destructor in C++ and how is it named?
Explanation
Destructors (~ClassName()) run automatically when objects go out of scope or are deleted, enabling RAII resource management.
Question 5 of 12
Why should a base class intended for polymorphic inheritance ALWAYS declare a virtual destructor (virtual ~Base() = default;)?
Explanation
Deleting a derived object via Base* without a virtual base destructor results in undefined behavior; the derived destructor never runs, leaking resources.
Question 6 of 12
What is dynamic polymorphism in C++ achieved through?
Explanation
Runtime polymorphism relies on 'virtual' functions. Compilers generate virtual tables (vtables) to resolve function calls dynamically at runtime.
Question 7 of 12
What is a pure virtual function and what does its declaration look like?
Explanation
A pure virtual function has '= 0' in its declaration, providing an interface contract that derived classes must implement.
Question 8 of 12
What is an abstract class in C++?
Explanation
Any class containing at least one pure virtual function is an abstract class. You cannot create direct instances of it, only pointers/references to derived classes.
Question 9 of 12
What does the 'override' specifier (introduced in C++11) guarantee when appended to a virtual function in a derived class?
Explanation
'override' prevents silent bugs where minor typo discrepancies in signatures create new overloaded functions instead of overriding the base method.
Question 10 of 12
What does the 'final' specifier do when applied to a class or virtual function in C++11?
Explanation
'final' forbids derived classes from inheriting from the class, or forbids derived classes from overriding the marked virtual function.
Question 11 of 12
What is constructor member initializer list syntax and why is it preferred over assignment inside the constructor body?
Explanation
Member initializer lists initialize fields directly upon allocation, avoiding double-initialization. They are mandatory for const members and references.
Question 12 of 12
What is the Diamond Problem in multiple inheritance and how is it resolved in C++?
Explanation
When two classes inherit from A and a fourth inherits from both, duplicate copies of A exist. 'virtual public A' ensures only one shared base subobject is created.
0 / 0 (0%)
Great job! Review the questions above and proceed when ready.
ADVERTISEMENT
Responsive Ad Unit • Technical Courses & Certifications